//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.baomidou.mybatisplus.samples.tenant.interceptor;

import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import com.baomidou.mybatisplus.core.toolkit.ClassUtils;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils.MPBoundSql;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils.MPStatementHandler;
import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.baomidou.mybatisplus.extension.toolkit.PropertyMapper;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.samples.tenant.handler.PlusTenantLineHandler;
import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExistsExpression;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.expression.operators.relational.ItemsList;
import net.sf.jsqlparser.expression.operators.relational.MultiExpressionList;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.AllColumns;
import net.sf.jsqlparser.statement.select.AllTableColumns;
import net.sf.jsqlparser.statement.select.FromItem;
import net.sf.jsqlparser.statement.select.Join;
import net.sf.jsqlparser.statement.select.LateralSubSelect;
import net.sf.jsqlparser.statement.select.ParenthesisFromItem;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectBody;
import net.sf.jsqlparser.statement.select.SelectExpressionItem;
import net.sf.jsqlparser.statement.select.SelectItem;
import net.sf.jsqlparser.statement.select.SetOperationList;
import net.sf.jsqlparser.statement.select.SubJoin;
import net.sf.jsqlparser.statement.select.SubSelect;
import net.sf.jsqlparser.statement.select.ValuesList;
import net.sf.jsqlparser.statement.select.WithItem;
import net.sf.jsqlparser.statement.update.Update;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

/**
 * 租户插件增强，新增自定义过滤条件 ignore
 */
public class PlusTenantLineInnerInterceptor extends JsqlParserSupport implements InnerInterceptor {
    private PlusTenantLineHandler tenantLineHandler;

    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        if (!tenantLineHandler.ignore(ms.getId())) {
            MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
            mpBs.sql(this.parserSingle(mpBs.sql(), (Object) null));
        }
    }

    /**
     * 解析sql前置方法
     */
    public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
        MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
        MappedStatement ms = mpSh.mappedStatement();
        SqlCommandType sct = ms.getSqlCommandType();
        if (sct == SqlCommandType.INSERT || sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) {
            if (InterceptorIgnoreHelper.willIgnoreTenantLine(ms.getId())) {
                // @InterceptorIgnore忽略的方法
                return;
            }

            if (tenantLineHandler.ignore(ms.getId())) {
                // ignore的方法
                return;
            }


            MPBoundSql mpBs = mpSh.mPBoundSql();
            mpBs.sql(this.parserMulti(mpBs.sql(), (Object) null));
        }

    }

    protected void processSelect(Select select, int index, String sql, Object obj) {
        this.processSelectBody(select.getSelectBody());
        List<WithItem> withItemsList = select.getWithItemsList();
        if (!CollectionUtils.isEmpty(withItemsList)) {
            withItemsList.forEach(this::processSelectBody);
        }

    }

    protected void processSelectBody(SelectBody selectBody) {
        if (selectBody != null) {
            if (selectBody instanceof PlainSelect) {
                this.processPlainSelect((PlainSelect) selectBody);
            } else if (selectBody instanceof WithItem) {
                WithItem withItem = (WithItem) selectBody;
                this.processSelectBody(withItem.getSubSelect().getSelectBody());
            } else {
                SetOperationList operationList = (SetOperationList) selectBody;
                List<SelectBody> selectBodys = operationList.getSelects();
                if (CollectionUtils.isNotEmpty(selectBodys)) {
                    selectBodys.forEach(this::processSelectBody);
                }
            }

        }
    }

    protected void processInsert(Insert insert, int index, String sql, Object obj) {
        if (!this.tenantLineHandler.ignoreTable(insert.getTable().getName())) {
            List<Column> columns = insert.getColumns();
            if (!CollectionUtils.isEmpty(columns)) {
                String tenantIdColumn = this.tenantLineHandler.getTenantIdColumn();
                if (!this.tenantLineHandler.ignoreInsert(columns, tenantIdColumn)) {
                    columns.add(new Column(tenantIdColumn));
                    List<Expression> duplicateUpdateColumns = insert.getDuplicateUpdateExpressionList();
                    if (CollectionUtils.isNotEmpty(duplicateUpdateColumns)) {
                        EqualsTo equalsTo = new EqualsTo();
                        equalsTo.setLeftExpression(new StringValue(tenantIdColumn));
                        equalsTo.setRightExpression(this.tenantLineHandler.insertTenantId());
                        duplicateUpdateColumns.add(equalsTo);
                    }

                    Select select = insert.getSelect();
                    if (select != null) {
                        this.processInsertSelect(select.getSelectBody());
                    } else {
                        if (insert.getItemsList() == null) {
                            throw ExceptionUtils.mpe("Failed to process multiple-table update, please exclude the tableName or statementId", new Object[0]);
                        }

                        ItemsList itemsList = insert.getItemsList();
                        if (itemsList instanceof MultiExpressionList) {
                            ((MultiExpressionList) itemsList).getExpressionLists().forEach((el) -> {
                                el.getExpressions().add(this.tenantLineHandler.insertTenantId());
                            });
                        } else {
                            ((ExpressionList) itemsList).getExpressions().add(this.tenantLineHandler.insertTenantId());
                        }
                    }

                }
            }
        }
    }

    protected void processUpdate(Update update, int index, String sql, Object obj) {
        Table table = update.getTable();
        if (!this.tenantLineHandler.ignoreTable(table.getName())) {
            update.setWhere(this.andExpression(table, update.getWhere()));
        }
    }

    protected void processDelete(Delete delete, int index, String sql, Object obj) {
        if (!this.tenantLineHandler.ignoreTable(delete.getTable().getName())) {
            delete.setWhere(this.andExpression(delete.getTable(), delete.getWhere()));
        }
    }

    protected Expression andExpression(Table table, Expression where) {
        final InExpression in = new InExpression(this.getAliasColumn(table), this.tenantLineHandler.selectTenantId());
        if (null != where) {
            return where instanceof OrExpression ? new AndExpression(in, new Parenthesis(where)) : new AndExpression(in, where);
        } else {
            return in;
        }
    }

    protected void processInsertSelect(SelectBody selectBody) {
        PlainSelect plainSelect = (PlainSelect) selectBody;
        FromItem fromItem = plainSelect.getFromItem();
        if (fromItem instanceof Table) {
            this.processPlainSelect(plainSelect);
            this.appendSelectItem(plainSelect.getSelectItems());
        } else if (fromItem instanceof SubSelect) {
            SubSelect subSelect = (SubSelect) fromItem;
            this.appendSelectItem(plainSelect.getSelectItems());
            this.processInsertSelect(subSelect.getSelectBody());
        }

    }

    protected void appendSelectItem(List<SelectItem> selectItems) {
        if (!CollectionUtils.isEmpty(selectItems)) {
            if (selectItems.size() == 1) {
                SelectItem item = (SelectItem) selectItems.get(0);
                if (item instanceof AllColumns || item instanceof AllTableColumns) {
                    return;
                }
            }

            selectItems.add(new SelectExpressionItem(new Column(this.tenantLineHandler.getTenantIdColumn())));
        }
    }

    protected void processPlainSelect(PlainSelect plainSelect) {
        List<SelectItem> selectItems = plainSelect.getSelectItems();
        if (CollectionUtils.isNotEmpty(selectItems)) {
            selectItems.forEach(this::processSelectItem);
        }

        Expression where = plainSelect.getWhere();
        this.processWhereSubSelect(where);
        FromItem fromItem = plainSelect.getFromItem();
        List<Table> list = this.processFromItem(fromItem);
        List<Table> mainTables = new ArrayList(list);
        List<Join> joins = plainSelect.getJoins();
        if (CollectionUtils.isNotEmpty(joins)) {
            mainTables = this.processJoins((List) mainTables, joins);
        }

        if (CollectionUtils.isNotEmpty((Collection) mainTables)) {
            plainSelect.setWhere(this.builderExpression(where, (List) mainTables));
        }

    }

    private List<Table> processFromItem(FromItem fromItem) {
        while (fromItem instanceof ParenthesisFromItem) {
            fromItem = ((ParenthesisFromItem) fromItem).getFromItem();
        }

        List<Table> mainTables = new ArrayList();
        if (fromItem instanceof Table) {
            Table fromTable = (Table) fromItem;
            if (!this.tenantLineHandler.ignoreTable(fromTable.getName())) {
                mainTables.add(fromTable);
            }
        } else if (fromItem instanceof SubJoin) {
            List<Table> tables = this.processSubJoin((SubJoin) fromItem);
            mainTables.addAll(tables);
        } else {
            this.processOtherFromItem(fromItem);
        }

        return mainTables;
    }

    protected void processWhereSubSelect(Expression where) {
        if (where != null) {
            if (where instanceof FromItem) {
                this.processOtherFromItem((FromItem) where);
            } else {
                if (where.toString().indexOf("SELECT") > 0) {
                    if (where instanceof BinaryExpression) {
                        BinaryExpression expression = (BinaryExpression) where;
                        this.processWhereSubSelect(expression.getLeftExpression());
                        this.processWhereSubSelect(expression.getRightExpression());
                    } else if (where instanceof InExpression) {
                        InExpression expression = (InExpression) where;
                        Expression inExpression = expression.getRightExpression();
                        if (inExpression instanceof SubSelect) {
                            this.processSelectBody(((SubSelect) inExpression).getSelectBody());
                        }
                    } else if (where instanceof ExistsExpression) {
                        ExistsExpression expression = (ExistsExpression) where;
                        this.processWhereSubSelect(expression.getRightExpression());
                    } else if (where instanceof NotExpression) {
                        NotExpression expression = (NotExpression) where;
                        this.processWhereSubSelect(expression.getExpression());
                    } else if (where instanceof Parenthesis) {
                        Parenthesis expression = (Parenthesis) where;
                        this.processWhereSubSelect(expression.getExpression());
                    }
                }

            }
        }
    }

    protected void processSelectItem(SelectItem selectItem) {
        if (selectItem instanceof SelectExpressionItem) {
            SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
            if (selectExpressionItem.getExpression() instanceof SubSelect) {
                this.processSelectBody(((SubSelect) selectExpressionItem.getExpression()).getSelectBody());
            } else if (selectExpressionItem.getExpression() instanceof Function) {
                this.processFunction((Function) selectExpressionItem.getExpression());
            }
        }

    }

    protected void processFunction(Function function) {
        ExpressionList parameters = function.getParameters();
        if (parameters != null) {
            parameters.getExpressions().forEach((expression) -> {
                if (expression instanceof SubSelect) {
                    this.processSelectBody(((SubSelect) expression).getSelectBody());
                } else if (expression instanceof Function) {
                    this.processFunction((Function) expression);
                }

            });
        }

    }

    protected void processOtherFromItem(FromItem fromItem) {
        while (fromItem instanceof ParenthesisFromItem) {
            fromItem = ((ParenthesisFromItem) fromItem).getFromItem();
        }

        if (fromItem instanceof SubSelect) {
            SubSelect subSelect = (SubSelect) fromItem;
            if (subSelect.getSelectBody() != null) {
                this.processSelectBody(subSelect.getSelectBody());
            }
        } else if (fromItem instanceof ValuesList) {
            this.logger.debug("Perform a subquery, if you do not give us feedback");
        } else if (fromItem instanceof LateralSubSelect) {
            LateralSubSelect lateralSubSelect = (LateralSubSelect) fromItem;
            if (lateralSubSelect.getSubSelect() != null) {
                SubSelect subSelect = lateralSubSelect.getSubSelect();
                if (subSelect.getSelectBody() != null) {
                    this.processSelectBody(subSelect.getSelectBody());
                }
            }
        }

    }

    private List<Table> processSubJoin(SubJoin subJoin) {
        List<Table> mainTables = new ArrayList();
        if (subJoin.getJoinList() != null) {
            List<Table> list = this.processFromItem(subJoin.getLeft());
            ((List) mainTables).addAll(list);
            mainTables = this.processJoins((List) mainTables, subJoin.getJoinList());
        }

        return (List) mainTables;
    }

    private List<Table> processJoins(List<Table> mainTables, List<Join> joins) {
        if (mainTables == null) {
            mainTables = new ArrayList();
        }

        Table mainTable = null;
        Table leftTable = null;
        if (((List) mainTables).size() == 1) {
            mainTable = (Table) ((List) mainTables).get(0);
            leftTable = mainTable;
        }

        Deque<List<Table>> onTableDeque = new LinkedList();
        Iterator var6 = joins.iterator();

        while (true) {
            while (true) {
                while (var6.hasNext()) {
                    Join join = (Join) var6.next();
                    FromItem joinItem = join.getRightItem();
                    List<Table> joinTables = null;
                    if (joinItem instanceof Table) {
                        joinTables = new ArrayList();
                        ((List) joinTables).add((Table) joinItem);
                    } else if (joinItem instanceof SubJoin) {
                        joinTables = this.processSubJoin((SubJoin) joinItem);
                    }

                    if (joinTables != null) {
                        if (join.isSimple()) {
                            ((List) mainTables).addAll((Collection) joinTables);
                        } else {
                            Table joinTable = (Table) ((List) joinTables).get(0);
                            boolean joinTableNeedIgnore = this.tenantLineHandler.ignoreTable(joinTable.getName());
                            List<Table> onTables = null;
                            if (join.isRight()) {
                                mainTable = joinTableNeedIgnore ? null : joinTable;
                                if (leftTable != null) {
                                    onTables = Collections.singletonList(leftTable);
                                }
                            } else if (join.isLeft()) {
                                if (!joinTableNeedIgnore) {
                                    onTables = Collections.singletonList(joinTable);
                                }
                            } else if (join.isInner()) {
                                if (mainTable == null) {
                                    onTables = Collections.singletonList(joinTable);
                                } else {
                                    onTables = Arrays.asList(mainTable, joinTable);
                                }

                                mainTable = null;
                            }

                            mainTables = new ArrayList();
                            if (mainTable != null) {
                                ((List) mainTables).add(mainTable);
                            }

                            Collection<Expression> originOnExpressions = join.getOnExpressions();
                            LinkedList onExpressions;
                            if (originOnExpressions.size() == 1 && onTables != null) {
                                onExpressions = new LinkedList();
                                onExpressions.add(this.builderExpression((Expression) originOnExpressions.iterator().next(), onTables));
                                join.setOnExpressions(onExpressions);
                                leftTable = joinTable;
                            } else {
                                onTableDeque.push(onTables);
                                if (originOnExpressions.size() > 1) {
                                    onExpressions = new LinkedList();
                                    Iterator var15 = originOnExpressions.iterator();

                                    while (var15.hasNext()) {
                                        Expression originOnExpression = (Expression) var15.next();
                                        List<Table> currentTableList = (List) onTableDeque.poll();
                                        if (CollectionUtils.isEmpty(currentTableList)) {
                                            onExpressions.add(originOnExpression);
                                        } else {
                                            onExpressions.add(this.builderExpression(originOnExpression, currentTableList));
                                        }
                                    }

                                    join.setOnExpressions(onExpressions);
                                }

                                leftTable = joinTable;
                            }
                        }
                    } else {
                        this.processOtherFromItem(joinItem);
                        leftTable = null;
                    }
                }

                return (List) mainTables;
            }
        }
    }

    protected Expression builderExpression(Expression currentExpression, List<Table> tables) {
        if (CollectionUtils.isEmpty(tables)) {
            return currentExpression;
        } else {
            // 获取租户的条件列表， 即tenant_id in(xxx,xxx,xxx) 里面的值， 如 1,2,3
            final ItemsList tenantIds = this.tenantLineHandler.selectTenantId();
            // 组装in条件
            List<Expression> equalsTos = tables.stream().map((item) -> new InExpression(this.getAliasColumn(item), tenantIds)).collect(Collectors.toList());
            Expression injectExpression = equalsTos.get(0);
            if (equalsTos.size() > 1) {
                for (int i = 1; i < equalsTos.size(); ++i) {
                    injectExpression = new AndExpression(injectExpression, equalsTos.get(i));
                }
            }

            if (currentExpression == null) {
                return (Expression) injectExpression;
            } else {
                return currentExpression instanceof OrExpression ? new AndExpression(new Parenthesis(currentExpression), (Expression) injectExpression) : new AndExpression(currentExpression, (Expression) injectExpression);
            }
        }
    }

    protected Column getAliasColumn(Table table) {
        StringBuilder column = new StringBuilder();
        if (table.getAlias() != null) {
            column.append(table.getAlias().getName());
        } else {
            column.append(table.getName());
        }

        column.append(".").append(this.tenantLineHandler.getTenantIdColumn());
        return new Column(column.toString());
    }

    public void setProperties(Properties properties) {
        PropertyMapper.newInstance(properties).whenNotBlank("tenantLineHandler", ClassUtils::newInstance, this::setTenantLineHandler);
    }

    public PlusTenantLineHandler getTenantLineHandler() {
        return this.tenantLineHandler;
    }

    public void setTenantLineHandler(final PlusTenantLineHandler tenantLineHandler) {
        this.tenantLineHandler = tenantLineHandler;
    }

    public PlusTenantLineInnerInterceptor() {
    }

    public PlusTenantLineInnerInterceptor(final PlusTenantLineHandler tenantLineHandler) {
        this.tenantLineHandler = tenantLineHandler;
    }

    public String toString() {
        return "TenantLineInnerInterceptor(super=" + super.toString() + ", tenantLineHandler=" + this.getTenantLineHandler() + ")";
    }

    public boolean equals(final Object o) {
        if (o == this) {
            return true;
        } else if (!(o instanceof PlusTenantLineInnerInterceptor)) {
            return false;
        } else {
            PlusTenantLineInnerInterceptor other = (PlusTenantLineInnerInterceptor) o;
            if (!other.canEqual(this)) {
                return false;
            } else if (!super.equals(o)) {
                return false;
            } else {
                Object this$tenantLineHandler = this.getTenantLineHandler();
                Object other$tenantLineHandler = other.getTenantLineHandler();
                if (this$tenantLineHandler == null) {
                    if (other$tenantLineHandler != null) {
                        return false;
                    }
                } else if (!this$tenantLineHandler.equals(other$tenantLineHandler)) {
                    return false;
                }

                return true;
            }
        }
    }

    protected boolean canEqual(final Object other) {
        return other instanceof PlusTenantLineInnerInterceptor;
    }

    public int hashCode() {
        int result = super.hashCode();
        Object $tenantLineHandler = this.getTenantLineHandler();
        result = result * 59 + ($tenantLineHandler == null ? 43 : $tenantLineHandler.hashCode());
        return result;
    }


}
